package com.fsh.lingsp.common.user.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.fsh.lingsp.common.user.dao.UserDao;
import com.fsh.lingsp.common.user.domain.dto.IpResult;
import com.fsh.lingsp.common.user.domain.entity.IpDetail;
import com.fsh.lingsp.common.user.domain.entity.IpInfo;
import com.fsh.lingsp.common.user.domain.entity.User;
import com.fsh.lingsp.common.user.service.IpService;
import com.fsh.lingsp.common.user.service.cache.UserCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class IpServiceImpl implements IpService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private UserCache userCache;


    //自定义线城池
    private static final ThreadPoolExecutor executor=new ThreadPoolExecutor(
            1,1,0, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(500),
            new NamedThreadFactory("refresh-ipDetail",null,false)
    );

    /**
     * 异步解析用户的 ip详情。使用线程池。
     *      原因：1.我们是在用户上线后做的，不能因为它阻塞主线程。
     *           2.淘宝的解析ip归属地接口慢，有频控。经过测试差不多1秒解析1个很稳定。
     *
     * @param uid 用户的id
     */
    @Override
    public void refreshDetailAsync(Long uid) {
        executor.execute(()->{
            //先查询出用户的基本信息
            User user = userDao.getById(uid);
            IpInfo ipInfo = user.getIpInfo();
            if (Objects.isNull(ipInfo)){
                return;
            }
            //找到要更新的ip（详情看方法内）
            String ip=ipInfo.needRefreshIp();
            if (StrUtil.isBlank(ip)){
                return;
            }
            //开始解析ip归属地
            IpDetail ipDetail=TryGetIpDetailOrNullThreeTimes(ip);
            if (Objects.nonNull(ipDetail)){
                //更新ipInfo里的字段属性
                ipInfo.refreshIpDetail(ipDetail);
                //构建User实体，插入数据库，更新ip_info字段
                User update=User.builder()
                        .id(uid)
                        .ipInfo(ipInfo).build();
                userDao.updateById(update);
                //用户信息改变，调用缓存变更的方法--》删除缓存，更新最后一次“更新用户信息的时间”。
                userCache.userInfoChange(uid);
            }else{
                //3次解析没有成功，打印日志
                log.error("IpServiceImpl.refreshDetailAsync()--->get ip detail fail ip:{},uid:{}",ip,uid);
            }
        });
    }

    // 解析ip归属地，因为淘宝接口有频控，所以可能会调用失败。
    // 那么我们对每一个ip给3次机会解析
    private IpDetail TryGetIpDetailOrNullThreeTimes(String ip) {
        for (int i = 0; i < 3; i++) {
            IpDetail ipDetail=getIpDetailOrNull(ip);
            if (Objects.nonNull(ipDetail)){
                return ipDetail;
            }
            //解析失败，休息2s重新解析
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        //3次机会都解析失败。不解析了，直接返回null。
        return null;
    }

    //调用淘宝接口，解析ip归属地。
    private IpDetail getIpDetailOrNull(String ip) {
        //使用hutool提供的 httpclient 工具类，发送请求。返回的数据是{"data":{},"msg":"","code":0/4}
        // code为0解析成功 ，4解析失败
        String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc");
        IpResult<IpDetail> result = JSONUtil.toBean(body, new TypeReference<IpResult<IpDetail>>() {
        }, false);
        if (result.isSuccess()){
            return result.getData();
        }
        //解析失败
        return null;
    }
}
